package phase1.part1;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;

/**
 * Class to handle reading the spec file.
 * 
 * @author Grant McFerrin
 */
public class SpecFileReader {
    private String inputFilename;

    private BufferedReader inputReader;
    private BufferedWriter outputWriter;
    
    private HashMap<String, String> characterClasses = new HashMap<String, String>();
    private HashMap<String, String> tokenDefinitions = new HashMap<String, String>();
    
    public SpecFileReader(String input, String output) {
        inputFilename = input;

        try {
            inputReader = new BufferedReader(new FileReader(input));
        }
        catch (FileNotFoundException fileNotFound) {
            fileNotFound.printStackTrace();
        }
        
        try {
            outputWriter = new BufferedWriter(new FileWriter(output));
        }
        catch (IOException io) {
            io.printStackTrace();
        }
    }
    
    public SpecFileReader(String input) {
        inputFilename = input;

        try {
            inputReader = new BufferedReader(new FileReader(input));
        }
        catch (FileNotFoundException fileNotFound) {
            fileNotFound.printStackTrace();
        }
    }
    
    public void scan() {
        try {
            int previousChar = 10; //Simulate newline character
            int nextChar = inputReader.read();
            
            String definition = "";
            String name = "";
            
            boolean isName = true;
            boolean isDefinition = false;
            
            boolean isCharacterClass = true;
            boolean isTokenDefinitions = false;

            boolean bracketOpen = false;
            boolean isExclusionSet = false;
            boolean isExclusionDefinition = false;
            String excludedSet = "";

            boolean isReferenceToken = false;
            String referenceToken = "";
            
            while (nextChar != -1) {
                if (previousChar != 10) {
                    /* Parse names */
                    if (isName) {
                        if (nextChar >= 65 && nextChar <= 90) {
                            name = name + (char)nextChar;
                        }
                        else {
                            isName = false;
                            isDefinition = true;
                        }
                    }
                    else {
                        if (nextChar >= 32 && nextChar <= 126) {
                            if (isCharacterClass) {
                                if (nextChar == 91) {
                                    bracketOpen = true;
                                    definition = definition + (char)nextChar;
                                }
                                else if (nextChar == 93) {
                                    bracketOpen = false;
                                    definition = definition + (char)nextChar;
                                }
                                else if (nextChar == 32 && bracketOpen) {
                                    definition = definition + (char)nextChar;
                                }
                                else if (nextChar == 32 && !bracketOpen && definition != "") {
                                    isExclusionSet = true;
                                }
                                
                                if (isExclusionSet) {
                                    if (previousChar == 36) {
                                        isExclusionDefinition = true;
                                    }
                                    
                                    if (nextChar == 91) {
                                        isExclusionDefinition = true;
                                    }
                                    
                                    if (isExclusionDefinition) {
                                        excludedSet = excludedSet + (char)nextChar;
                                    }
                                }
                            }
                            else {
                                if (isReferenceToken) {
                                    if (nextChar >= 65 && nextChar <= 90) {
                                        referenceToken = referenceToken + (char)nextChar;
                                    }
                                    else {
                                        definition = definition + characterClasses.get(referenceToken);
                                        isReferenceToken = false;
                                    }
                                }
                                else {
                                    if (nextChar == 36 && previousChar != 92) {
                                        isReferenceToken = true;
                                    }
                                    else {
                                        definition = definition + (char)nextChar;
                                    }
                                }
                            }
                        }
                        else if (nextChar == 10){
                            if (previousChar == 10) {
                                isTokenDefinitions = true;
                                isCharacterClass = false;
                            }
                            else if (isCharacterClass && !isExclusionSet) {
                                characterClasses.put(name, definition);
                            }
                            else if (isExclusionSet) {
                                if (excludedSet.charAt(0) == 91) {
                                    handleExclusionSets(excludedSet, definition, name);
                                }
                                else {
                                    handleExclusionSets(characterClasses.get(excludedSet), definition, name);
                                }
                            }
                            else {
                                tokenDefinitions.put(name, definition);
                            }
                            
                            name = "";
                            definition = "";
                        }
                    }
                }
                
                previousChar = nextChar;
                nextChar = inputReader.read();
            }
        }
        catch (IOException io) {
            io.printStackTrace();
        }
    }
    
    private void handleExclusionSets(String characterSet, String exclusionSet, String name) {
        String workingCharSet = characterSet.substring(1, characterSet.length() - 1);
        String workingExclusionSet = characterSet.substring(2, characterSet.length() - 1);
        
        List<Character> includedChars = new ArrayList<Character>();
        List<Character> excludedChars = new ArrayList<Character>();
        
        String newRegex = "[";
        
        for (int i = 0; i < workingCharSet.length(); i++) {
            int currentChar = workingCharSet.charAt(i);
            
            if (currentChar == 92) {
                int nextChar = workingCharSet.charAt(i + 1);
                
                if ((nextChar >= 91  && nextChar <= 94) || nextChar == 45) {
                    includedChars.add((char)nextChar);
                    i++;
                }
            }
            else if (currentChar == 45) {
                if (workingCharSet.charAt(i - 1) != 92) {
                    int rangeStart = workingCharSet.charAt(i - 1);
                    int rangeEnd = workingCharSet.charAt(i + 1);
                    
                    for (int j = rangeStart; j <= rangeEnd; j++) {
                        includedChars.add((char) j);
                    }
                    
                    i++;
                }
            }
            else {
                if (i + 1 < workingCharSet.length()) {
                    int nextChar = workingCharSet.charAt(i + 1);
                    
                    if (nextChar != 45) {
                        includedChars.add((char)currentChar);
                    }
                }
            }
        }
        
        for (int i = 0; i < workingExclusionSet.length(); i++) {
            int currentChar = workingExclusionSet.charAt(i);
            
            if (currentChar == 92) {
                int nextChar = workingExclusionSet.charAt(i + 1);
                
                if ((nextChar >= 91  && nextChar <= 94) || nextChar == 45) {
                    excludedChars.add((char)nextChar);
                    i++;
                }
            }
            else if (currentChar == 45) {
                if (workingExclusionSet.charAt(i - 1) != 92) {
                    int rangeStart = workingExclusionSet.charAt(i - 1);
                    int rangeEnd = workingExclusionSet.charAt(i + 1);
                    
                    for (int j = rangeStart; j <= rangeEnd; j++) {
                        excludedChars.add((char) j);
                    }
                    
                    i++;
                }
            }
            else {
                if (i + 1 < workingExclusionSet.length()) {
                    int nextChar = workingExclusionSet.charAt(i + 1);
                    
                    if (nextChar != 45) {
                        excludedChars.add((char)currentChar);
                    }
                }
            }
        }
        
        includedChars.removeAll(excludedChars);
        
        for (int i = 0; i < includedChars.size(); i++) {
            newRegex = newRegex + includedChars.get(i).toString();
        }
        
        newRegex = newRegex + "]";
        
        characterClasses.put(name, newRegex);
    }
}
